﻿using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Xml.Serialization;

namespace GeneralTool.CoreLibrary.Licenses
{
    /// <summary>
    /// 密钥管理
    /// </summary>
    public static class LicenseManager
    {
        /// <summary>
        /// 默认的密钥路径
        /// </summary>
        public static string DefaultLicensPath { get; } = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "licnese.licx");

        /// <summary>
        /// 默认的密钥请求路径
        /// </summary>
        public static string DefaultRequestLIcensPath { get; } = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "request.licx");

        private static string deviceId = "";

        /// <summary>
        /// 获取密钥信息
        /// </summary>
        /// <param name="licenseFilePath">密钥路径,如果为空,则默认使用 licnese.licx</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public static LicenseInfo GetLicenseInfo(ref string licenseFilePath)
        {
            if (string.IsNullOrWhiteSpace(licenseFilePath))
                licenseFilePath = DefaultLicensPath;

            if (!File.Exists(licenseFilePath))
                ThrowAndRequest($"没有找到密钥文件 {licenseFilePath} ");


            //读取文件
            var txt = File.ReadAllLines(licenseFilePath);
            var bytes = Decode(txt);
            var xml = new XmlSerializer(typeof(LicenseInfo));
            using (var ms = new MemoryStream(bytes))
            {
                var obj = xml.Deserialize(ms);
                return (LicenseInfo)obj;
            }
        }

        /// <summary>
        /// 校验密钥
        /// </summary>
        /// <param name="licenseFilePath">密钥路径,如果为空,则默认使用 licnese.licx</param>
        /// <exception cref="ExpireException"></exception>
        /// <exception cref="ExpireOldException"></exception>
        public static LicenseInfo Verity(string licenseFilePath, bool verfiyDeviceId = false)
        {
            var now = DateTime.Now;
            var info = GetLicenseInfo(ref licenseFilePath);
            //查看当前时间是否已经超过密钥有效期
            if (info.ExpireTime < now)
                ThrowAndRequest($"密钥已经过期,过期时间:{DateTime.Now - info.ExpireTime}");

            //查看当前的时间是否比上一次的时间还要小
            if (info.PrevUseTime > now)
                ThrowAndRequest("电脑当前时间可能被修改");

            if (info.PrevUseTime < info.CreateTime)
                ThrowAndRequest("电脑当前时间可能被修改,请更正");

            if (info.FreeTime < TimeSpan.Zero)
                ThrowAndRequest($"密钥已经过期,过期时间:{info.FreeTime}");

            //查看Id是否一致
            if (verfiyDeviceId)
            {
                if (info.DeviceHardId != GetDeviceId())
                    ThrowAndRequest("硬件无法对应,不可使用该密钥 ");
            }

            info.PrevUseTime = now;
            info = UpdateCurrTime(info, ref licenseFilePath);
            return info;
        }

        /// <summary>
        /// 获取设备唯一ID
        /// </summary>
        /// <returns></returns>
        public static string GetDeviceId()
        {
            if (!string.IsNullOrWhiteSpace(deviceId))
                return deviceId;
            var cpuId = ComputerHelper.CpuId();
            var biosId = ComputerHelper.BiosId();
            deviceId = $"{cpuId}&{biosId}";
            return deviceId;
        }

        private static void ThrowAndRequest(string msg)
        {
            //创建请求文件
            CreateRequestLicense(DefaultRequestLIcensPath);
            throw new ExpireOldException($"{msg}");
        }

        /// <summary>
        /// 更新密钥时间
        /// </summary>
        /// <param name="licenseFilePath"></param>
        /// <param name="expireTime"></param>
        /// <returns></returns>
        public static LicenseInfo UpdateExpriseTime(ref string licenseFilePath, DateTime expireTime)
        {
            var licx = GetLicenseInfo(ref licenseFilePath);
            licx.PrevUseTime = DateTime.Now;
            licx.ExpireTime = expireTime;
            return UpdateCurrTime(licx, ref licenseFilePath);
        }


        /// <summary>
        /// 更新密钥
        /// </summary>
        /// <param name="licenseFilePath">密钥路径,如果为空,则默认使用 licnese.licx</param>
        public static LicenseInfo UpdateCurrTime(ref string licenseFilePath)
        {
            var licx = GetLicenseInfo(ref licenseFilePath);
            licx.PrevUseTime = DateTime.Now;
            return UpdateCurrTime(licx, ref licenseFilePath);
        }

        public static LicenseInfo UpdateCurrTime(LicenseInfo licx, ref string licenseFilePath)
        {
            var bytes = CreateLicense(licx);

            File.WriteAllText(licenseFilePath, Encode(bytes));
            return licx;
        }

        /// <summary>
        /// 创建密钥
        /// </summary>
        /// <param name="createDeviceId">设备唯一ID</param>
        /// <param name="expireTime">过期时间</param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="Exception"></exception>
        public static byte[] CreateLicense(bool createDeviceId, DateTime expireTime)
        {
            if (expireTime <= DateTime.Now.AddMinutes(5))
                throw new Exception("过期时间需要比当前时间大于5分钟以上");

            var info = new LicenseInfo()
            {
                CreateTime = DateTime.Now,
                DeviceHardId = createDeviceId ? GetDeviceId() : "",
                ExpireTime = expireTime,
                PrevUseTime = null
            };


            return CreateLicense(info);
        }

        /// <summary>
        /// 创建密钥
        /// </summary>
        /// <param name="licenseInfo"></param>
        /// <returns></returns>
        public static byte[] CreateLicense(LicenseInfo licenseInfo)
        {
            //创建密钥文本
            var xml = new XmlSerializer(typeof(LicenseInfo));
            using (var ms = new MemoryStream())
            {
                xml.Serialize(ms, licenseInfo);
                return ms.ToArray();
            }
        }

        /// <summary>
        /// 保存到文件
        /// </summary>
        /// <param name="savePath"></param>
        /// <param name="createDeviceId"></param>
        /// <param name="expireTime"></param>
        public static void SaveToFile(string savePath, bool createDeviceId, DateTime expireTime)
        {
            var licByte = CreateLicense(createDeviceId, expireTime);
            File.WriteAllText(savePath, Encoding.ASCII.GetString(licByte));
        }

        /// <summary>
        /// 创建密钥请求文件
        /// </summary>
        /// <param name="savePath">保存的路径</param>
        public static void CreateRequestLicense(string savePath, bool bindDevice = true)
        {
            var licenseInfo = new LicenseInfo()
            {
                DeviceHardId = bindDevice ? $"{ComputerHelper.CpuId()}&{ComputerHelper.BiosId()}" : "000000",
                CreateTime = DateTime.Now,
                PrevUseTime = DateTime.Now,
                ExpireTime = DateTime.Now
            };


            if (string.IsNullOrWhiteSpace(savePath))
                savePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "request.licx");

            var bytes = CreateLicense(licenseInfo);

            File.WriteAllText(savePath, Encode(bytes));
        }

        /// <summary>
        /// 保存密钥
        /// </summary>
        /// <param name="license"></param>
        public static void SaveToFile(LicenseInfo license, string savePath)
        {
            var bytes = CreateLicense(license);
            if (string.IsNullOrWhiteSpace(savePath))
                savePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "request.licx");
            File.WriteAllText(savePath, Encode(bytes));
        }

        public static string Encode(byte[] datas)
        {
            var builder = new StringBuilder();
            for (int i = 0; i < datas.Length; i++)
            {
                builder.AppendLine((datas[i] * 100) + "");
            }
            return builder.ToString();
        }

        public static byte[] Decode(string[] lines)
        {
            var arr = new byte[lines.Length];

            for (int i = 0; i < lines.Length; i++)
            {
                arr[i] = (byte)(Convert.ToInt32(lines[i]) / 100);
            }
            return arr;
        }

        private static bool isLoop;
        /// <summary>
        /// 循环进行更新使用时间与进行测试
        /// </summary>
        /// <param name="licxPath">密钥路径,如果为空,则默认使用 licnese.licx</param>
        /// <param name="expireAction">检测到异常时的处理方法</param>
        public static void LoopUpdateAndVertiy(string licxPath, Action<string> expireAction)
        {
            if (!isLoop)
                new Thread(LoopVertiy) { IsBackground = true }.Start(new object[] { licxPath, expireAction });
        }

        private static void LoopVertiy(object obj)
        {
            isLoop = true;
            var pas = obj as object[];

            var path = pas[0] + "";
            var action = pas[1] as Action<string>;

            while (true)
            {
                try
                {
                    Verity(path);
                }
                catch (Exception ex)
                {
                    action?.Invoke(ex.Message);
                }
            }
        }
    }
}
